/* ws2812b_lpit_edma.c */
/*======================================*/
/*=== Author: Elvis Chen             ===*/
/*=== eMail : 39343228@qq.com        ===*/
/*=== Date  : 2017/6/3               ===*/
/*======================================*/

#include "app_inc.h"
#include "ws2812b_gpio.h"
#include "ws2812b_lpit_edma.h"
#include "hal_gpio.h"
#include "hal_dmamux.h"
#include "hal_edma.h"
#include "hal_trgmux.h"

/*
* 本组件使用LPIT定时触发eDMA操作GPIO产生精准的WS2812B操作时序，不再依赖于编译器的效率和设置
*/
uint32_t RgbLeftData[BSP_WS2812B_LED_COUNT], RgbRightData[BSP_WS2812B_LED_COUNT];
bool DmaIsProcessing;

//#define WS2812B_1BIT_CONTROL_TIMES  25U    /* (0.85 + 0.4)/0.05 = 25 */
#define WS2812B_1BIT_CONTROL_TIMES  6U    /* (0.85 + 0.4)/0.05 = 25 */

#define GPIO_TOGGLE_RIGHT_RGB_CONTROL   0x00000008
#define GPIO_TOGGLE_LEFT_RGB_CONTROL    0x00000004
#define GPIO_TOGGLE_NONE                0x00000000

uint32_t GpioToggleControlData[WS2812B_DATA_BIT_COUNT_MAX * BSP_WS2812B_LED_COUNT * WS2812B_1BIT_CONTROL_TIMES];

/* EDMA传输通道配置模板 */
#define WS2812B_LPIT_EDMA_CONFIG_DEFAULT_STRUCT {              \
    .SrcAddr                     = 0U,                      \
    .DestAddr                    = 0U,                      \
    .MinorLoopByteCount          = 0U,                      \
    .SrcBusWidthMode             = eEDMA_BusWidth_4Byte,    \
    .DestBusWidthMode            = eEDMA_BusWidth_4Byte,    \
    .SrcAddrCycleMode            = eEDMA_AddrCycle_Disabled,\
    .DestAddrCycleMode           = eEDMA_AddrCycle_Disabled,\
    .SrcAddrIncPerTransfer       = 0,                       \
    .DestAddrIncPerTransfer      = 0,                       \
    .SrcAddrMinorLoopDoneOffset  = 0,                       \
    .DestAddrMinorLoopDoneOffset = 0,                       \
    .MinorLoopCount              = 0,                       \
    .SrcAddrMajorLoopDoneOffset  = 0,                       \
    .DestAddrMajorLoopDoneOffset = 0,                       \
}

#define WS2812B_LPIT_EDMA_CHN_INDEX   3U

void LPIT0_CHN3_EDMA_Start()
{
    EDMA_TransferConfig_T edmaTransferConfigStruct = WS2812B_LPIT_EDMA_CONFIG_DEFAULT_STRUCT;

    /* 配置EDMA通道之前确保通道是关闭的 */
    DMAMUX_HAL_SetTriggerForDMA(WS2812B_LPIT_EDMA_CHN_INDEX, eDMAMUX_TriggerSource_Disabled);
    EDMA_HAL_ResetChannel(WS2812B_LPIT_EDMA_CHN_INDEX);

    edmaTransferConfigStruct.SrcAddr = (uint32_t)(GpioToggleControlData);
    edmaTransferConfigStruct.SrcAddrIncPerTransfer = 4U;                            /* 在队列内递增4字节 */
    edmaTransferConfigStruct.DestAddr = (uint32_t)(&(BSP_GPIO_WS2812B_RIGHT_PORT->PTOR));    /* 左右两组WS2812B的控制口位于同一个PORT口，故目的地址是同一个 */
    edmaTransferConfigStruct.MinorLoopCount = WS2812B_DATA_BIT_COUNT_MAX * BSP_WS2812B_LED_COUNT * WS2812B_1BIT_CONTROL_TIMES;
    edmaTransferConfigStruct.MinorLoopByteCount = 4U; /* 每次传输四个字节 */
    EDMA_HAL_ConfigTransfer(WS2812B_LPIT_EDMA_CHN_INDEX, &edmaTransferConfigStruct);

    /* 启用传输完成中断 */
    NVIC_EnableIRQ(DMA3_IRQn); /* 在NVIC中开启监听发送搬运完成中断信号 */
    EDMA_HAL_EnableInterruptOnMajorLoopDone(WS2812B_LPIT_EDMA_CHN_INDEX, true);

    /* 解除对DMA通道的锁定，启动发送过程 */
    EDMA_HAL_EnableHwTrigger(WS2812B_LPIT_EDMA_CHN_INDEX, true); /* 启用硬件触发 */
    EDMA_HAL_SetHwTriggerAutoDisabledOnMajorLoopDone(WS2812B_LPIT_EDMA_CHN_INDEX, true); /* 发送完成自动关闭硬件触发 */
    DMAMUX_HAL_SetTriggerForDMA(WS2812B_LPIT_EDMA_CHN_INDEX, eDMAMUX_TriggerSource_PIT); /* 放开EDMA触发信号的大门 */
    
    /* 把LPIT0的通道3配置到DMA通道3的触发请求端口上 */
    TRGMUX_SetTriggerSource(TRGMUX0, kTRGMUX_Dmamux0, kTRGMUX_TriggerInput3, kTRGMUX_SourceLpit0Ch3);
}

void Ws2812b_ConvertBitToGpioToggleData(uint32_t *RgbLeftDataBuf, uint32_t *RgbRightDataBuf)
{
    uint32_t i, bitCount, ledCount, bufIndex;
    uint32_t colorLeft, colorRight;
   
    for(ledCount = 0U; ledCount < BSP_WS2812B_LED_COUNT; ledCount++)
    {
        colorLeft = RgbLeftDataBuf[ledCount];
        colorRight = RgbRightDataBuf[ledCount];
        
        for(bitCount = 0U; bitCount < WS2812B_DATA_BIT_COUNT_MAX; bitCount++)
        {
            for(i = 0; i < WS2812B_1BIT_CONTROL_TIMES; i++)
            {
                bufIndex = i + 
                           bitCount*WS2812B_1BIT_CONTROL_TIMES + 
                           ledCount*WS2812B_DATA_BIT_COUNT_MAX*WS2812B_1BIT_CONTROL_TIMES;
                if(i == 0 ) 
                {
                    GpioToggleControlData[bufIndex] = GPIO_TOGGLE_RIGHT_RGB_CONTROL + GPIO_TOGGLE_LEFT_RGB_CONTROL;                    
                } 
                else if(i == 2) /* 0.4us 节点检查bit值，如果为0则翻转GPIO产生下降沿 */
                {
                    /* 计算左侧RGB翻转控制值 */
                    if (0U == (colorLeft & (1U << (WS2812B_DATA_BIT_COUNT_MAX-1U - bitCount)))) /* bit value = 0. */
                    {
                        GpioToggleControlData[bufIndex] = GPIO_TOGGLE_LEFT_RGB_CONTROL;                                            
                    }
                    else
                    {
                        GpioToggleControlData[bufIndex] = GPIO_TOGGLE_NONE;                                                                    
                    }
                    /* 计算右侧RGB翻转控制值 */
                    if (0U == (colorRight & (1U << (WS2812B_DATA_BIT_COUNT_MAX-1U - bitCount)))) /* bit value = 0. */
                    {
                        GpioToggleControlData[bufIndex] |= GPIO_TOGGLE_RIGHT_RGB_CONTROL;                                            
                    }
                    else
                    {
                        GpioToggleControlData[bufIndex] &= (~GPIO_TOGGLE_RIGHT_RGB_CONTROL);                                                                    
                    }
                }
                else if(i == 4) /* 0.85us 节点检查bit值，如果为0则翻转GPIO产生下降沿 */
                {
                    /* 计算左侧RGB翻转控制值 */
                    if (0U == (colorLeft & (1U << (WS2812B_DATA_BIT_COUNT_MAX-1U - bitCount)))) /* bit value = 0. */
                    {
                        GpioToggleControlData[bufIndex] = GPIO_TOGGLE_NONE;                                                                    
                    }
                    else
                    {
                        GpioToggleControlData[bufIndex] = GPIO_TOGGLE_LEFT_RGB_CONTROL;                                            
                    }
                    /* 计算右侧RGB翻转控制值 */
                    if (0U == (colorRight & (1U << (WS2812B_DATA_BIT_COUNT_MAX-1U - bitCount)))) /* bit value = 0. */
                    {
                        GpioToggleControlData[bufIndex] &= (~GPIO_TOGGLE_RIGHT_RGB_CONTROL);                                                                    
                    }
                    else
                    {
                        GpioToggleControlData[bufIndex] |= GPIO_TOGGLE_RIGHT_RGB_CONTROL;                                            
                    }
                }
                else
                {
                    GpioToggleControlData[bufIndex] = GPIO_TOGGLE_NONE;                                                                                       
                }                    
            }            
        }
    }    
}

void Ws2812b_GenerateControlTiming(uint32_t *RgbLeftDataBuf, uint32_t *RgbRightDataBuf)
{
    Ws2812b_ConvertBitToGpioToggleData(RgbLeftDataBuf, RgbRightDataBuf);
    DmaIsProcessing = true;
    LPIT0_CHN3_EDMA_Start();
    LPIT_HAL_StartChannel(LPIT0, APP_LPIT_RGB_CONTROL_CHANNEL);
    while(DmaIsProcessing == true); /* 等待DMA传输完成 */
}

void Ws2812b_UpdateColorBuf(uint32_t *ColorDataBuf, uint32_t led1_color, uint32_t led2_color, uint32_t led3_color)
{
    ColorDataBuf[0] = led1_color;
    ColorDataBuf[1] = led2_color;
    ColorDataBuf[2] = led3_color;    
}


/* EDMA完成RGBLED GPIO时序发生后中断服务程序入口 */
void DMA3_IRQHandler(void)
{
    /* 停用发送完成中断 */
    EDMA_HAL_EnableInterruptOnMajorLoopDone(WS2812B_LPIT_EDMA_CHN_INDEX, false);
    NVIC_DisableIRQ(DMA3_IRQn);

    LPIT_HAL_StopChannel(LPIT0, APP_LPIT_RGB_CONTROL_CHANNEL);
    
    /* DMA触发窗口在搬运完成后自动关闭，此处不需要手动关闭窗口，但需要手动清除完成标志位 */
    EDMA_HAL_ClearMajorLoopDoneFlag(WS2812B_LPIT_EDMA_CHN_INDEX);
    EDMA_HAL_ClearInterruptFlagOfMajorLoopDone(WS2812B_LPIT_EDMA_CHN_INDEX);
    DMAMUX_HAL_SetTriggerForDMA(WS2812B_LPIT_EDMA_CHN_INDEX, eDMAMUX_TriggerSource_Disabled);
    
    DmaIsProcessing = false;
}

/* EOF. */


